package cn.tedu.charging.device.service;

import cn.tedu.charging.common.pojo.po.ChargingGunInfoPO;
import cn.tedu.charging.common.pojo.po.ChargingStationPO;
import cn.tedu.charging.common.pojo.query.NearStationsQuery;
import cn.tedu.charging.common.pojo.vo.GunInfoVO;
import cn.tedu.charging.common.pojo.vo.StationDetailVO;
import cn.tedu.charging.common.pojo.vo.StationInfoVO;
import cn.tedu.charging.device.mapper.GunMapper;
import cn.tedu.charging.device.mapper.StationMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.geo.*;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.GeoOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Service
@Slf4j
public class DeviceServiceImpl implements DeviceService{
    //station的持久层
    @Autowired
    private StationMapper stationMapper;
    @Autowired
    private RedisTemplate redisTemplate;
    @Override
    public List<StationInfoVO> nearStations(NearStationsQuery query) {
        log.debug("查询附近场站数据,入参:{}",query);
        //1.读取入参 命中的附近数据 元素值id 地理位置 距离
        List<GeoResult> geoMembers=getNearStations(query);
        //2.判断
        if (geoMembers==null||geoMembers.size()==0){
            log.debug("没有查到任何场站数据");
            return null;
        }
        //3.根据命中的元素,读取数据po
        List<ChargingStationPO> stationPos=getStationPos(geoMembers);
        log.debug("获取数据层返回的pos:{}",stationPos);
        //4.封装结果返回
        List<StationInfoVO> vos=parseToStationInfoVos(stationPos,geoMembers);
        //log.debug("封装结果返回,{}",vos);
        return vos;
    }
    /*
        将查询的geo集合 与数据库持久化po列表 用来转化成返回的vo集合
     */
    private List<StationInfoVO> parseToStationInfoVos(List<ChargingStationPO> stationPos, List<GeoResult> geoMembers) {
        //1.for循环写
        List<StationInfoVO> vos=new ArrayList<>();
        for (int i=0;i<stationPos.size();i++){
            //每循环一次 都封装一个vo 填满数据属性
            StationInfoVO vo=new StationInfoVO();
            vo.setStationId(stationPos.get(i).getId());
            vo.setStationLng(stationPos.get(i).getStationLng());
            vo.setStationLat(stationPos.get(i).getStationLat());
            vo.setStationName(stationPos.get(i).getStationName());
            //distance 从geoResult
            GeoResult<RedisGeoCommands.GeoLocation> geoResult = geoMembers.get(i);
            double distance=geoResult.getDistance().getValue();//8.9952 取2位小数点 四舍五入 直接删除
            vo.setDistance(new BigDecimal(distance).setScale(2, RoundingMode.HALF_UP).doubleValue());
            //使用
            vos.add(vo);
        }
        //2.stream() map 使用filter过滤
        return vos;
    }
    /*
        利用redis命中的geo集合元素 查询station的持久化列表
     */
    private List<ChargingStationPO> getStationPos(List<GeoResult> geoMembers) {
        //做的是映射 每次映射拿到一个geoResult 对应 查询封装 po 查询sql where id= member
        return geoMembers.stream().map(geoResult->{
            //1.取元素 Integer stationId
            RedisGeoCommands.GeoLocation content = (RedisGeoCommands.GeoLocation) geoResult.getContent();
            Integer id= (Integer) content.getName();
            //使用id读取stationPO 将流程封装
            return getStationPoById(id);
        }).collect(Collectors.toList());
    }
    /*
        单独封装一个复用的cache-aside读取流程 专门读取station缓存对象
     */
    private ChargingStationPO getStationPoById(Integer id) {
        //2.读取stationPo cache-aside
        String stationCacheKey="charging:station:cache:"+id;
        ValueOperations stringOps = redisTemplate.opsForValue();
        //2.1读缓存 key和id绑定 前缀 后缀
        ChargingStationPO poCache= (ChargingStationPO) stringOps.get(stationCacheKey);
        if (poCache!=null){
            log.debug("缓存命中,直接返回");
            return poCache;
        }else{
            log.debug("缓存未命中,读取数据库,返回:{}",id);
            //2.2命中直接返回 没命中读取数据库
            ChargingStationPO po = stationMapper.selectById(id);
            //如果数据库没有 说明是一个缓存穿透 可能性 极小
            stringOps.set(stationCacheKey,po);
            return po;
        }
    }

    /*
        调用redis客户端API 查询 圆圈范围内的geo集合元素
     */
    private List<GeoResult> getNearStations(NearStationsQuery query) {
        //1.key和预热key相同 charging:stations:all
        String geoKey="charging:stations:all";
        //2.geo类型 拿到子对象
        GeoOperations geoOps = redisTemplate.opsForGeo();
        //3.提供命令调用georadius lng lat radius(5000) km withcoord withdist
        //3.1提供选项
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs();
        args.includeCoordinates().includeDistance();
        //3.2封装中心点 和半径 提供圆的范围
        Point center=new Point(query.getLongitude(), query.getLatitude());
        Distance radius=new Distance(query.getRadius(), RedisGeoCommands.DistanceUnit.KILOMETERS);
        Circle circle=new Circle(center,radius);
        //3.3调用查询 获取list<GeoLocation>
        List<GeoResult> results = geoOps.radius(geoKey, circle, args).getContent();
        log.debug("查询的结果:{}",results);
        return results;
    }
    @Autowired
    private GunMapper gunMapper;
    /*
        第二个接口的业务实现 根据入参场站id 查询场站详情
     */
    @Override
    public StationDetailVO detailStation(Integer stationId) {
        //1.读缓存 cache-aside读流程
        ChargingStationPO stationPO=getStationPoById(stationId);
        //2.判断这个po能拿到么
        if (stationPO==null){
            //如果是null应该满足穿透的结果
            return null;
        }
        //3.使用stationId属性 查询关联的枪
        List<ChargingGunInfoPO> gunPos = gunMapper.selectByStationId(stationPO.getId());
        //4.使用先有数据 stationPO 和gunPos封装一个stationDetailVO
        return parseToStationDetailVo(stationPO,gunPos);
    }

    private StationDetailVO parseToStationDetailVo(ChargingStationPO stationPO,
                                                   List<ChargingGunInfoPO> gunPos) {
        StationDetailVO vo=new StationDetailVO();
        vo.setStationName(stationPO.getStationName());
        vo.setAddress(stationPO.getAddress());
        //每个场站vo对象对应一批gunInfoVO
        if (gunPos!=null&&gunPos.size()>0){
            //映射gunVO放到stationVO里
            List<GunInfoVO> gunVos = gunPos.stream().map(po -> {
                GunInfoVO gunVo = new GunInfoVO();
                BeanUtils.copyProperties(po, gunVo);
                return gunVo;
            }).collect(Collectors.toList());
            vo.setGunInfoPOList(gunVos);
        }
        return vo;
    }

    @Override
    public Object updateGunStatus(Integer gunId, Integer status) {
        return null;
    }
}
